﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using HandyControl.Data;

namespace HandyControl.Interactivity;

public sealed class FluidMoveBehavior : FluidMoveBehaviorBase
{
    private static readonly DependencyProperty CacheDuringOverlayProperty =
        DependencyProperty.RegisterAttached("CacheDuringOverlay", typeof(object),
            typeof(FluidMoveBehavior), new PropertyMetadata(null));

    public static readonly DependencyProperty DurationProperty = DependencyProperty.Register("Duration",
        typeof(Duration), typeof(FluidMoveBehavior),
        new PropertyMetadata(new Duration(TimeSpan.FromSeconds(1.0))));

    public static readonly DependencyProperty EaseXProperty = DependencyProperty.Register("EaseX",
        typeof(IEasingFunction), typeof(FluidMoveBehavior), new PropertyMetadata(null));

    public static readonly DependencyProperty EaseYProperty = DependencyProperty.Register("EaseY",
        typeof(IEasingFunction), typeof(FluidMoveBehavior), new PropertyMetadata(null));

    public static readonly DependencyProperty FloatAboveProperty =
        DependencyProperty.Register("FloatAbove", typeof(bool), typeof(FluidMoveBehavior),
            new PropertyMetadata(ValueBoxes.TrueBox));

    private static readonly DependencyProperty HasTransformWrapperProperty =
        DependencyProperty.RegisterAttached("HasTransformWrapper", typeof(bool),
            typeof(FluidMoveBehavior), new PropertyMetadata(ValueBoxes.FalseBox));

    private static readonly DependencyProperty InitialIdentityTagProperty =
        DependencyProperty.RegisterAttached("InitialIdentityTag", typeof(object),
            typeof(FluidMoveBehavior), new PropertyMetadata(null));

    public static readonly DependencyProperty InitialTagPathProperty =
        DependencyProperty.Register("InitialTagPath", typeof(string), typeof(FluidMoveBehavior),
            new PropertyMetadata(string.Empty));

    public static readonly DependencyProperty InitialTagProperty =
        DependencyProperty.Register("InitialTag", typeof(TagType), typeof(FluidMoveBehavior),
            new PropertyMetadata(TagType.Element));

    private static readonly DependencyProperty OverlayProperty =
        DependencyProperty.RegisterAttached("Overlay", typeof(object), typeof(FluidMoveBehavior),
            new PropertyMetadata(null));

    private static readonly Dictionary<object, Storyboard> TransitionStoryboardDictionary =
        new();

    public Duration Duration
    {
        get =>
            (Duration) GetValue(DurationProperty);
        set => SetValue(DurationProperty, value);
    }

    public IEasingFunction EaseX
    {
        get =>
            (IEasingFunction) GetValue(EaseXProperty);
        set => SetValue(EaseXProperty, value);
    }

    public IEasingFunction EaseY
    {
        get =>
            (IEasingFunction) GetValue(EaseYProperty);
        set => SetValue(EaseYProperty, value);
    }

    public bool FloatAbove
    {
        get =>
            (bool) GetValue(FloatAboveProperty);
        set => SetValue(FloatAboveProperty, ValueBoxes.BooleanBox(value));
    }

    public TagType InitialTag
    {
        get =>
            (TagType) GetValue(InitialTagProperty);
        set => SetValue(InitialTagProperty, value);
    }

    public string InitialTagPath
    {
        get =>
            (string) GetValue(InitialTagPathProperty);
        set => SetValue(InitialTagPathProperty, value);
    }

    protected override bool ShouldSkipInitialLayout
    {
        get
        {
            if (!base.ShouldSkipInitialLayout) return InitialTag == TagType.DataContext;
            return true;
        }
    }

    private static void AddTransform(FrameworkElement child, Transform transform)
    {
        if (!(child.RenderTransform is TransformGroup renderTransform))
        {
            renderTransform = new TransformGroup
            {
                Children = { child.RenderTransform }
            };
            child.RenderTransform = renderTransform;
            SetHasTransformWrapper(child, true);
        }

        renderTransform.Children.Add(transform);
    }

    private Storyboard CreateTransitionStoryboard(FrameworkElement child, bool usingBeforeLoaded,
        ref Rect layoutRect, ref Rect currentRect)
    {
        var duration = Duration;
        var storyboard = new Storyboard
        {
            Duration = duration
        };
        var num = !usingBeforeLoaded || Math.Abs(layoutRect.Width) < 0.001
            ? 1.0
            : currentRect.Width / layoutRect.Width;
        var num2 = !usingBeforeLoaded || Math.Abs(layoutRect.Height) < 0.001
            ? 1.0
            : currentRect.Height / layoutRect.Height;
        var num3 = currentRect.Left - layoutRect.Left;
        var num4 = currentRect.Top - layoutRect.Top;
        var group = new TransformGroup();
        var transform = new ScaleTransform
        {
            ScaleX = num,
            ScaleY = num2
        };
        group.Children.Add(transform);
        var transform2 = new TranslateTransform
        {
            X = num3,
            Y = num4
        };
        group.Children.Add(transform2);
        AddTransform(child, group);
        var str = "(FrameworkElement.RenderTransform).";
        if (child.RenderTransform is TransformGroup renderTransform && GetHasTransformWrapper(child))
        {
            object obj2 = str;
            str = string.Concat(obj2, "(TransformGroup.Children)[", renderTransform.Children.Count - 1,
                "].");
        }

        if (usingBeforeLoaded)
        {
            if (Math.Abs(num - 1.0) > 0.001)
            {
                var element = new DoubleAnimation
                {
                    Duration = duration,
                    From = num,
                    To = 1.0
                };
                Storyboard.SetTarget(element, child);
                Storyboard.SetTargetProperty(element,
                    new PropertyPath(str + "(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"));
                element.EasingFunction = EaseX;
                storyboard.Children.Add(element);
            }

            if (Math.Abs(num2 - 1.0) > 0.001)
            {
                var animation3 = new DoubleAnimation
                {
                    Duration = duration,
                    From = num2,
                    To = 1.0
                };
                Storyboard.SetTarget(animation3, child);
                Storyboard.SetTargetProperty(animation3,
                    new PropertyPath(str + "(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"));
                animation3.EasingFunction = EaseY;
                storyboard.Children.Add(animation3);
            }
        }

        if (Math.Abs(num3) > 0.001)
        {
            var animation5 = new DoubleAnimation
            {
                Duration = duration,
                From = num3,
                To = 0.0
            };
            Storyboard.SetTarget(animation5, child);
            Storyboard.SetTargetProperty(animation5,
                new PropertyPath(str + "(TransformGroup.Children)[1].(TranslateTransform.X)"));
            animation5.EasingFunction = EaseX;
            storyboard.Children.Add(animation5);
        }

        if (Math.Abs(num4) > 0.001)
        {
            var animation7 = new DoubleAnimation
            {
                Duration = duration,
                From = num4,
                To = 0.0
            };
            Storyboard.SetTarget(animation7, child);
            Storyboard.SetTargetProperty(animation7,
                new PropertyPath(str + "(TransformGroup.Children)[1].(TranslateTransform.Y)"));
            animation7.EasingFunction = EaseY;
            storyboard.Children.Add(animation7);
        }

        return storyboard;
    }

    protected override void EnsureTags(FrameworkElement child)
    {
        base.EnsureTags(child);
        if (InitialTag == TagType.DataContext &&
            !(child.ReadLocalValue(InitialIdentityTagProperty) is BindingExpression))
            child.SetBinding(InitialIdentityTagProperty, new Binding(InitialTagPath));
    }

    private static bool GetHasTransformWrapper(DependencyObject obj)
    {
        return (bool) obj.GetValue(HasTransformWrapperProperty);
    }

    private static object GetInitialIdentityTag(DependencyObject obj)
    {
        return obj.GetValue(InitialIdentityTagProperty);
    }

    private static object GetOverlay(DependencyObject obj)
    {
        return obj.GetValue(OverlayProperty);
    }

    private static Transform GetTransform(FrameworkElement child)
    {
        if (child.RenderTransform is TransformGroup renderTransform && renderTransform.Children.Count > 0)
            return renderTransform.Children[renderTransform.Children.Count - 1];
        return new TranslateTransform();
    }

    private static bool IsClose(double a, double b)
    {
        return Math.Abs(a - b) < 1E-07;
    }

    private static bool IsEmptyRect(Rect rect)
    {
        if (!rect.IsEmpty && !double.IsNaN(rect.Left)) return double.IsNaN(rect.Top);
        return true;
    }

    private static void RemoveTransform(FrameworkElement child)
    {
        if (child.RenderTransform is TransformGroup renderTransform)
        {
            if (GetHasTransformWrapper(child))
            {
                child.RenderTransform = renderTransform.Children[0];
                SetHasTransformWrapper(child, false);
            }
            else
            {
                renderTransform.Children.RemoveAt(renderTransform.Children.Count - 1);
            }
        }
    }

    private static void SetHasTransformWrapper(DependencyObject obj, bool value)
    {
        obj.SetValue(HasTransformWrapperProperty, ValueBoxes.BooleanBox(value));
    }

    private static void SetOverlay(DependencyObject obj, object value)
    {
        obj.SetValue(OverlayProperty, value);
    }

    private static void TransferLocalValue(FrameworkElement element, DependencyProperty source,
        DependencyProperty dest)
    {
        var obj2 = element.ReadLocalValue(source);
        if (obj2 is BindingExpressionBase base2)
            element.SetBinding(dest, base2.ParentBindingBase);
        else if (obj2 == DependencyProperty.UnsetValue)
            element.ClearValue(dest);
        else
            element.SetValue(dest, element.GetAnimationBaseValue(source));
        element.ClearValue(source);
    }

    internal override void UpdateLayoutTransitionCore(FrameworkElement child, FrameworkElement root,
        object tag, TagData newTagData)
    {
        Rect empty;
        var flag = false;
        var usingBeforeLoaded = false;
        var initialIdentityTag = GetInitialIdentityTag(child);
        var flag3 = TagDictionary.TryGetValue(tag, out var data);
        if (flag3 && data.InitialTag != initialIdentityTag)
        {
            flag3 = false;
            TagDictionary.Remove(tag);
        }

        if (!flag3)
        {
            if (initialIdentityTag != null && TagDictionary.TryGetValue(initialIdentityTag, out var data2))
            {
                empty = TranslateRect(data2.AppRect, root, newTagData.Parent);
                flag = true;
                usingBeforeLoaded = true;
            }
            else
            {
                empty = Rect.Empty;
            }

            data = new TagData
            {
                ParentRect = Rect.Empty,
                AppRect = Rect.Empty,
                Parent = newTagData.Parent,
                Child = child,
                Timestamp = DateTime.Now,
                InitialTag = initialIdentityTag
            };
            TagDictionary.Add(tag, data);
        }
        else if (!Equals(data.Parent, VisualTreeHelper.GetParent(child)))
        {
            empty = TranslateRect(data.AppRect, root, newTagData.Parent);
            flag = true;
        }
        else
        {
            empty = data.ParentRect;
        }

        var originalChild = child;
        if (!IsEmptyRect(empty) && !IsEmptyRect(newTagData.ParentRect) &&
            (!IsClose(empty.Left, newTagData.ParentRect.Left) ||
             !IsClose(empty.Top, newTagData.ParentRect.Top)) || !Equals(child, data.Child) &&
            TransitionStoryboardDictionary.ContainsKey(tag))
        {
            var rect = empty;
            var flag4 = false;
            if (TransitionStoryboardDictionary.TryGetValue(tag, out var storyboard))
            {
                var obj3 = GetOverlay(data.Child);
                var adorner = (AdornerContainer) obj3;
                flag4 = obj3 != null;
                var element = data.Child;
                if (obj3 != null)
                {
                    if (adorner.Child is Canvas canvas) element = canvas.Children[0] as FrameworkElement;
                }

                if (!usingBeforeLoaded) rect = GetTransform(element).TransformBounds(rect);
                TransitionStoryboardDictionary.Remove(tag);
                storyboard.Stop();
                RemoveTransform(element);
                if (obj3 != null)
                {
                    AdornerLayer.GetAdornerLayer(root).Remove(adorner);
                    TransferLocalValue(data.Child, CacheDuringOverlayProperty,
                        UIElement.RenderTransformProperty);
                    SetOverlay(data.Child, null);
                }
            }

            object overlay = null;
            if (flag4 || flag && FloatAbove)
            {
                var canvas2 = new Canvas
                {
                    Width = newTagData.ParentRect.Width,
                    Height = newTagData.ParentRect.Height,
                    IsHitTestVisible = false
                };
                var rectangle = new Rectangle
                {
                    Width = newTagData.ParentRect.Width,
                    Height = newTagData.ParentRect.Height,
                    IsHitTestVisible = false,
                    Fill = new VisualBrush(child)
                };
                canvas2.Children.Add(rectangle);
                var container2 = new AdornerContainer(child)
                {
                    Child = canvas2
                };
                overlay = container2;
                SetOverlay(originalChild, overlay);
                AdornerLayer.GetAdornerLayer(root).Add(container2);
                TransferLocalValue(child, UIElement.RenderTransformProperty, CacheDuringOverlayProperty);
                child.RenderTransform = new TranslateTransform(-10000.0, -10000.0);
                canvas2.RenderTransform = new TranslateTransform(10000.0, 10000.0);
                child = rectangle;
            }

            var parentRect = newTagData.ParentRect;
            var transitionStoryboard =
                CreateTransitionStoryboard(child, usingBeforeLoaded, ref parentRect, ref rect);
            TransitionStoryboardDictionary.Add(tag, transitionStoryboard);
            transitionStoryboard.Completed += delegate
            {
                if (TransitionStoryboardDictionary.TryGetValue(tag, out var storyboard1) &&
                    Equals(storyboard1, transitionStoryboard))
                {
                    TransitionStoryboardDictionary.Remove(tag);
                    transitionStoryboard.Stop();
                    RemoveTransform(child);
                    child.InvalidateMeasure();
                    if (overlay != null)
                    {
                        AdornerLayer.GetAdornerLayer(root).Remove((AdornerContainer) overlay);
                        TransferLocalValue(originalChild, CacheDuringOverlayProperty,
                            UIElement.RenderTransformProperty);
                        SetOverlay(originalChild, null);
                    }
                }
            };
            transitionStoryboard.Begin();
        }

        data.ParentRect = newTagData.ParentRect;
        data.AppRect = newTagData.AppRect;
        data.Parent = newTagData.Parent;
        data.Child = newTagData.Child;
        data.Timestamp = newTagData.Timestamp;
    }
}
